/* This file is part of BORG.
 * BORG is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *  
 * BORG is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *   
 * You should have received a copy of the GNU General Public License
 * along with BORG; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 */
package net.sf.borg.model;

import java.io.BufferedWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;

import net.sf.borg.model.beans.Appointment;

/**
 * CalHTMLWriter
 * 
 * This class implements the CalWriter interface.  CalHTMLWriter
 * provides an HTML representation of the BORG calendar from startDate
 * to endDate.  This representation provides a <div> in which lists
 * of appointments are written by their dates, followed by another
 * <div> which contains a tabular representation of a calendar for
 * the specified starting and ending dates.
 * 
 * Created in UF Software Engineering - Summer 2009
 * @author Matthew Carroll
 */

public class CalHTMLWriter implements CalWriter {
	
	public static CalHTMLWriter instance = null;
	
	public static CalHTMLWriter getInstance()
	{
		if(instance == null) {
			instance = new CalHTMLWriter();
		}
		
		return instance;
	}
	
	private CalHTMLWriter() {}

	/*
	 * This function could be made more efficient by using some kind of XML tree class.  If we used a tree system
	 * then we could write the appointment list and the calendar data at the same time, but since we are writing
	 * directly to a stream we must loop twice to allow for serialized writing - otherwise we would have pieces of
	 * the calendar in the middle of the appointment list.
	 */
	public void writeCal(BufferedWriter bw, Date startDate, Date endDate)
	{
		//Build beginning of HTML
		openHTML(bw);
		
		
		//Define variables used when iterating through dates
		int day;
    	int month;
    	int year;
    	int dkey;						//base key into the appointment model for a particular day
    	AppointmentModel model;
    	Collection<Integer> apptKeys;	//all appointments for the current day given by cycleCal
    	
		
		//Write opening HTML for appt area
    	openApptArea(bw);
    	
		Calendar cycleCal = new GregorianCalendar();// calendar used for iterating through dates
		long diff = endDate.getTime() - startDate.getTime();		// diff in time between start and end dates
	    int numDates = (int)Math.ceil(diff / (1000 * 60 * 60 * 24));// diff in days between start and end date
	    
	    //Iterate through dates and print appointments in list format to HTML	
	    cycleCal.setTime(startDate);	//init our calendar to our starting date
	    for( int i = 0; i <= numDates; i++)
	    {
	    	day = cycleCal.get(Calendar.DAY_OF_MONTH);
	    	month = cycleCal.get(Calendar.MONTH);
	    	year = cycleCal.get(Calendar.YEAR);
	    	dkey = AppointmentModel.dkey(year, month, day);	
	    	model = AppointmentModel.getReference();
	    	apptKeys = model.getAppts(dkey);
	    	
	    	if(apptKeys != null) {
	    		openApptList(bw, cycleCal);
		    	for( Integer apptKey : apptKeys ) {
		    		try{
		    			//Write appt to an HTML list item
		    			writeAppt(bw, model.getAppt(apptKey) );
		    		}catch(Exception e){}
		    	}
		    	closeApptList(bw);
	    	}
	    	
	    	//Iterate date
	    	cycleCal.add(Calendar.DAY_OF_MONTH, 1);
	    }
	    //END iteration of appointment list items
	    
	    //Write closing HTML for appt area
    	closeApptArea(bw);
	    
    	
	    //Start Calendar Table HTML
	    cycleCal.setTime(startDate);
	    openCal(bw, cycleCal);
	    
	    //Iterate through dates and print HTML for calendar of those dates
	    cycleCal.setTime(startDate);
	    for( int i = 0; i <= numDates; i++)
	    {
	    	day 		= cycleCal.get(Calendar.DAY_OF_MONTH);
	    	month 		= cycleCal.get(Calendar.MONTH);
	    	year 		= cycleCal.get(Calendar.YEAR);
	    	dkey 		= AppointmentModel.dkey(year, month, day);
	    	model 		= AppointmentModel.getReference();
	    	apptKeys 	= model.getAppts(dkey);
	    	
	    	Collection<Appointment> appts = null;	//all appts for a given day
	    	
	    	if(apptKeys != null) {
		    	appts = new ArrayList<Appointment>();
		    	for( Integer apptKey : apptKeys ) {
		    		try{
		    			appts.add( model.getAppt(apptKey) );
		    		}catch(Exception e){}
		    	}
	    	}
	    	
	    	writeDate(bw, cycleCal, appts);
	    	
	    	//Iterate date
	    	cycleCal.add(Calendar.DAY_OF_MONTH, 1);
	    }
	    //END iteration through calendar cells
	    
	    //Close calendar HTML
	    cycleCal.setTime(endDate);
	    closeCal(bw, cycleCal);
	    
	    
	    
	    //Write closing HTML for file
	    closeHTML(bw);
	    try{
	    	bw.flush();
	    }catch(Exception e) {}
	}
	
	/*
	 * Write HTML to open entire appt area
	 */
	private void openApptArea(BufferedWriter bw) 
	{
		try {
			bw.newLine();
			bw.write("    <div id=\"allTasks\">");
			bw.newLine();
		}catch (IOException e) {}
	}
	
	/*
	 * Writes HTML to <code>bw</code> that prepares HTML stream
	 * to write appointments
	 */
	private void openApptList(BufferedWriter bw, Calendar cal)
	{
		SimpleDateFormat sdf = new SimpleDateFormat();
		sdf.applyPattern("MMMMM dd', 'yyyy");
		String dateOutput = sdf.format(cal.getTime());
		
		try {
			bw.write("    <h1>" + dateOutput + "</h1>");
			bw.newLine();
			bw.write("    <ul>");
			bw.newLine();
		}catch (IOException e) {}
	}
	
	/*
	 * Writes an HTML appointment list item to stream bw
	 * 
	 * NOTE:  Call openApptList once before writing a given list of appointments, and
	 * 		  call closeApptList once after writing a given list of appointments
	 */
	private void writeAppt(BufferedWriter bw, Appointment appt)
	{
		String apptText = appt.getText();
		//There is no log of the appt title other than a newline character,
		//so split the appt text at the newline and take preceding text for
		//title, and following text for content
		int newLineIndex = apptText.indexOf('\n');	
		
		String title = "";
		String text  = "";
		if(newLineIndex >= 0) {
			title = apptText.substring(0, newLineIndex);
			text  = apptText.substring(newLineIndex + 1);
		}else{
			title = apptText;
		}
		
		try {
			bw.write("      <li>");
			bw.newLine();
			bw.write("         <div id=\"" + appt.getKey() + "\"><h3>" + title + "</h3>");
			bw.newLine();
			bw.write("         <p>" + text + "</p></div>");
			bw.newLine();
			bw.write("      </li>");
			bw.newLine();
		}catch (IOException e) {}
	}
	
	
	/*
	 * Closes out an HTML appointment list by writing to stream <code>bw</code>
	 */
	private void closeApptList(BufferedWriter bw)
	{
		try {
			bw.write("    </ul>");
			bw.newLine();
		}catch (IOException e) {}
	}
	
	/*
	 * Write HTMLM to close out the entire appointment description area
	 */
	private void closeApptArea(BufferedWriter bw) 
	{
		try {
			bw.write("    </div>");
			bw.newLine();
		}catch (IOException e) {}
	}
	
	
	
	/*
	 * Writes initial calendar HTML to <code>bw</code>
	 */
	private void openCal(BufferedWriter bw, Calendar startDate)
	{
		//Get number of days between first Sunday of first week and the first day.  We get
		//this difference so that we can fill in empty calendar cells that have no date
		//provided for them, but must be included in a rectangular table
		Calendar baseCal = (Calendar)startDate.clone();
		baseCal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);//startDate.getMinimum(Calendar.DAY_OF_WEEK));
		long diff = startDate.getTime().getTime() - baseCal.getTime().getTime();
	    int numDates = (int)Math.ceil(diff / (1000 * 60 * 60 * 24));
		
		try {
			bw.newLine();
			bw.write("    <div id=\"calendarArea\">");
			bw.newLine();
			bw.write("    <table>");
			bw.newLine();
			bw.write("      <tr>");
			bw.newLine();
			bw.write("      <th>Sunday</th>");
			bw.newLine();
			bw.write("      <th>Monday</th>");
			bw.newLine();
			bw.write("      <th>Tuesday</th>");
			bw.newLine();
			bw.write("      <th>Wednesday</th>");
			bw.newLine();
			bw.write("      <th>Thursday</th>");
			bw.newLine();
			bw.write("      <th>Friday</th>");
			bw.newLine();
			bw.write("      <th>Saturday</th>");
			bw.newLine();
			bw.write("      </tr>");
			bw.newLine();
			
			//If the start day is not Sunday, then build
			//empty cells to account for days leading up to our time period
			if(diff > 0) {
				bw.write("      <tr>");
				bw.newLine();
			}
			for( int i = 0; i < numDates; i++ ) {
				bw.write("        <td>&nbsp;</td>");
				bw.newLine();
			}
		}catch (IOException e) {}
	}
	
	/*
	 * Writes a given number (or zero) of appointments to the calendar - if no appointments
	 * are supplied, this function writes an empty calendar cell to the HTML stream
	 * 
	 * Note:  This should be called for every single date that is to appear in the calendar,
	 * 		  not just the ones that contain apointments.
	 */
	private void writeDate(BufferedWriter bw, Calendar cal, Collection<Appointment> appts)
	{
		SimpleDateFormat sdf = new SimpleDateFormat();
		sdf.applyPattern("MMMMM dd', 'yyyy");
		String dateOutput = sdf.format(cal.getTime());
		
		try {
			if(cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
				bw.write("      <tr>");
				bw.newLine();
			}
			
			bw.write("        <td>");
			bw.newLine();
			bw.write("          <h2>" + dateOutput + "</h2>");
			bw.newLine();
			
			if(appts != null) {
				for( Appointment appt : appts ) {
					String apptText = appt.getText();
					int newLineIndex = apptText.indexOf('\n');
					String title = "";
					if( newLineIndex >= 0) {
						title = apptText.substring(0, newLineIndex);
					}else{
						title = apptText;
					}
					
					bw.write("          <a href=\"#" + appt.getKey() + "\">" + title + "</a><br />");
					bw.newLine();
				}
			}
			
			bw.write("        </td>");
			bw.newLine();
			
			if(cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
				bw.write("      </tr>");
				bw.newLine();
			}
		}catch (IOException e) {}
	}
	
	
	/*
	 * Write closing HTML to <code>bw</code> for the calendar
	 */
	private void closeCal(BufferedWriter bw, Calendar lastDate)
	{		
	    int numDates = Calendar.SATURDAY - lastDate.get(Calendar.DAY_OF_WEEK);
	    
		try {
			for( int i = 0; i < numDates; i++ ) {
				bw.write("        <td>&nbsp;</td>");
				bw.newLine();
			}
			if( numDates > 0 ) {
				bw.write("      </tr>");
				bw.newLine();
			}
			bw.write("    </table>");
			bw.newLine();
			bw.write("    </div>");
			bw.newLine();
		}catch (IOException e) {}
	}
	
	/*
	 * Writes HTML to start an HTML web document with no <HEAD>
	 */
	private void openHTML(BufferedWriter bw)
	{
		try {
			bw.write("<html>");
			bw.newLine();
			bw.write("  <body>");
			bw.newLine();
		}catch (IOException e) {}
	}
	
	/*
	 * Writes HTML to end an HTML web document
	 */
	private void closeHTML(BufferedWriter bw)
	{
		try {
			bw.write("  <body>");
			bw.newLine();
			bw.write("<html>");
		}catch (IOException e) {}
	}
	
}
